﻿/**************************************************************
 *
 * 唯一标识：f576ca5e-3131-4d0b-af27-a8d94ed6e944
 * 命名空间：Sgr.BackGroundTasks
 * 创建时间：2023/9/2 17:17:27
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：基于 Quartz.NET 的周期性任务管理器实现
 *
 **************************************************************/

using Microsoft.Extensions.Options;
using Quartz;
using Sgr.Utilities;

namespace Sgr.BackGroundTasks
{
    /// <summary>
    /// 基于 Quartz.NET 的周期性任务管理器实现
    /// </summary>
    /// <remarks>
    /// 负责管理系统中的周期性任务，包括启动、停止和重新调度。
    /// </remarks>
    public class QuartzRecurringTaskManager : IRecurringTaskManager
    {
        public const string RecurringGroupName = "sgr-recurring";

        private readonly ISchedulerFactory _schedulerFactory;
        private readonly BackGroundTaskOptions _backGroundTaskOptions;
        private readonly ILogger<QuartzRecurringTaskManager>? _logger;

        /// <summary>
        /// 初始化 QuartzRecurringTaskManager 的新实例
        /// </summary>
        /// <param name="factory">Quartz 调度器工厂</param>
        /// <param name="backGroundTaskOptions">后台任务配置选项</param>
        /// <param name="logger">日志记录器（可选）</param>
        public QuartzRecurringTaskManager(
            ISchedulerFactory factory,
            IOptions<BackGroundTaskOptions> backGroundTaskOptions,
            ILogger<QuartzRecurringTaskManager>? logger = null)
        {
            _schedulerFactory = factory ?? throw new ArgumentNullException(nameof(factory));
            _backGroundTaskOptions = backGroundTaskOptions?.Value ?? throw new ArgumentNullException(nameof(backGroundTaskOptions));
            _logger = logger;
        }

        /// <summary>
        /// 重启任务（如果任务已存在，那么会先停止再重新启动）
        /// </summary>
        /// <param name="recurringTaskId">周期性任务 ID</param>
        /// <param name="taskData">任务数据（可选）</param>
        /// <param name="cronExpressions">Cron 表达式（可选）</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>表示异步操作的任务</returns>
        /// <exception cref="ArgumentException">当 recurringTaskId 为 null 或空时抛出</exception>
        /// <exception cref="BackGroundTaskException">当任务停止或启动失败时抛出</exception>
        public async Task ReStartAsync(string recurringTaskId,
            object? taskData = null,
            string? cronExpressions = null,
            CancellationToken cancellationToken = default)
        {
            Check.StringNotNullOrWhiteSpace(recurringTaskId, nameof(recurringTaskId));

            //获取并更新周期性任务描述信息
            var recurringTaskDefine = _backGroundTaskOptions.GetRecurringTaskMetadata(recurringTaskId);

            // 更新任务数据和 Cron 表达式（如果提供）
            if (taskData != null)
            {
                if (recurringTaskDefine.TaskMetadata.TaskDataType != null &&
                    !recurringTaskDefine.TaskMetadata.TaskDataType.IsInstanceOfType(taskData))
                {
                    throw new ArgumentException(
                        $"任务数据类型不匹配。任务: {recurringTaskDefine.Name}, " +
                        $"期望类型: {recurringTaskDefine.TaskMetadata.TaskDataType.FullName}, " +
                        $"实际类型: {taskData.GetType().FullName}",
                        nameof(taskData));
                }

                recurringTaskDefine.TaskData = taskData;
            }

            if (cronExpressions != null)
            {
                if (!CronExpression.IsValidExpression(cronExpressions))
                    throw new ArgumentException($"无效的 Cron 表达式: {cronExpressions}", nameof(cronExpressions));

                recurringTaskDefine.CronExpressions = cronExpressions;
            }

            // 获取调度器
            var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);

            //先确保任务已停止
            var jobKey = GetJobKey(recurringTaskId);
            if (await IsJobRunningAsync(scheduler, jobKey, cancellationToken))
            {
                await StopInternalAsync(scheduler, recurringTaskId, cancellationToken);

                if (await IsJobRunningAsync(scheduler, jobKey, cancellationToken))
                    throw new BackGroundTaskException($"停止任务(Task Id = {recurringTaskId})失败!");
            }

            // 准备任务数据
            string serializedData = JsonHelper.SerializeObject(recurringTaskDefine.TaskData);

            // 创建作业
            var job = JobBuilder.Create<QuartzPeriodicJob>()
                .WithIdentity(jobKey)
                .UsingJobData(QuartzBackGroundTaskManager.KEY_TASK_CLASS_TYPE, recurringTaskDefine.TaskMetadata.TaskClassType.FullName)
                .UsingJobData(QuartzBackGroundTaskManager.KEY_DATA_OBJECT, serializedData)
                .Build();

            // 创建触发器
            var triggerKey = GetTriggerKey(recurringTaskId);
            ITrigger trigger;

            if (!string.IsNullOrWhiteSpace(recurringTaskDefine.CronExpressions))
            {
                // 创建基于 Cron 表达式的触发器
                trigger = TriggerBuilder.Create()
                            .WithIdentity(triggerKey)
                            .WithCronSchedule(recurringTaskDefine.CronExpressions)
                            .Build();
            }
            else
            {
                // 创建简单触发器（立即执行一次）
                trigger = TriggerBuilder.Create()
                    .WithIdentity(triggerKey)
                    .StartNow()
                    .Build();
            }
            // 调度作业
            await scheduler.ScheduleJob(job, trigger, cancellationToken);
        }

        /// <summary>
        /// 停止任务
        /// </summary>
        /// <param name="recurringTaskId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task StopAsync(string recurringTaskId,
            CancellationToken cancellationToken = default)
        {
            // 获取调度器
            var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);

            // 执行停止操作
            await StopInternalAsync(scheduler, recurringTaskId, cancellationToken);
        }

        /// <summary>
        /// 内部停止任务方法
        /// </summary>
        /// <param name="scheduler">Quartz 调度器</param>
        /// <param name="recurringTaskId">周期性任务 ID</param>
        /// <param name="cancellationToken">取消令牌</param>
        protected async Task StopInternalAsync(
            IScheduler scheduler,
            string recurringTaskId,
            CancellationToken cancellationToken = default)
        {
            Check.NotNull(scheduler, nameof(scheduler));
            Check.StringNotNullOrWhiteSpace(recurringTaskId, nameof(recurringTaskId));

            // 取消触发器
            var triggerKey = GetTriggerKey(recurringTaskId);
            var jobKey = GetJobKey(recurringTaskId);

            // 首先取消触发器
            bool unscheduled = await scheduler.UnscheduleJob(triggerKey, cancellationToken);

            if (unscheduled)
            {
                _logger?.LogDebug("已取消周期性任务的触发器: {TaskId}", recurringTaskId);
            }
            else
            {
                _logger?.LogWarning("未找到周期性任务的触发器: {TaskId}", recurringTaskId);
            }

            // 然后删除作业
            if (await scheduler.CheckExists(jobKey, cancellationToken))
            {
                if (await IsJobRunningAsync(scheduler, jobKey, cancellationToken))
                {
                    _logger?.LogWarning("正在停止运行中的任务: {TaskId}", recurringTaskId);
                }

                await scheduler.DeleteJob(jobKey, cancellationToken);
                _logger?.LogDebug("已删除周期性任务: {TaskId}", recurringTaskId);
            }
        }

        private static async Task<bool> IsJobRunningAsync(IScheduler scheduler, JobKey jobKey, CancellationToken cancellationToken)
        {
            var currentlyExecutingJobs = await scheduler.GetCurrentlyExecutingJobs(cancellationToken);
            return currentlyExecutingJobs.Any(job => job.JobDetail.Key.Equals(jobKey));
        }

        private static JobKey GetJobKey(string recurringTaskId)
        {
            return new JobKey("Job-" + recurringTaskId, RecurringGroupName);
        }

        private static TriggerKey GetTriggerKey(string recurringTaskId)
        {
            return new TriggerKey("Trigger-" + recurringTaskId, RecurringGroupName);
        }
    }
}